﻿using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Security.Principal;
using System.Threading;
using Toodledo.Model;
using Toodledo.Security;
using Toodledo.Services.Interfaces;
using Toodledo.Services.Helpers;

namespace Toodledo.Services
{
    public class AuthenticationService : ToodledoServiceBase
    {
        private readonly string _ticketFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ToodledoTicket.txt");

        public AuthenticationService(IToodledoConnector connector) : base(connector)
        {
        }

        public void Authenticate(string userEmail, string password)
        {
            try
            {
                var authTicket = GetToodledoAuthTicket(userEmail, password);
                var identity   = new ToodledoIdentity(userEmail, password, authTicket.UserId, authTicket.Token, authTicket.AuthKey);
                var principal  = new ToodledoPrincipal(identity, new[] { "user" });
                Thread.CurrentPrincipal = principal;
            }
            catch (ArgumentException)
            {
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("annonymous"), new[] { "annonymous" });
            }
        }

        public AccountInfo GetAccountInfo()
        {
            var identity = Thread.CurrentPrincipal.Identity as ToodledoIdentity;
            if (identity != null)
            {
                var queryParams = new NameValueCollection
                                      {
                                          {"method", "getAccountInfo"},
                                          {"key", identity.AuthKey}
                                      };
                var response = _toodledoConnector.GetToodledoResponse(queryParams);

                var accountInfo = new AccountInfo
                                      {
                                          UserId = response.Root.GetElementValue("userid"),
                                          Alias = response.Root.GetElementValue("alias"),
                                          Pro = response.Root.GetElementValue("pro")
                                      };
                return accountInfo;
            }
            throw new UnauthorizedAccessException("You are not signed in to view the account information");
        }
        
        #region Authentication Helpers
        private ToodledoAuthTicket GetToodledoAuthTicket(string userEmail, string password)
        {
            // if the auth ticket exists in cache deserialize it
            ToodledoAuthTicket ticket = ReadAuthTicketFromFile(_ticketFile);

            //if the auth ticket is still valid return the same
            if (ticket != null && ticket.Email == userEmail)
            {
                if (ticket.Md5Password == password.ComputeMd5HashString())
                {
                    if (ticket.ExpiryTime > DateTime.Now)
                    {
                        return ticket;
                    }
                }
            }
            //else fetch new auth ticket
            var freshAuthTicket = GetFreshAuthTicket(userEmail, password);
            if (freshAuthTicket != null)
            {
                WriteTicketToFile(freshAuthTicket);
                return freshAuthTicket;
            }
            return null;
        }

        private ToodledoAuthTicket GetFreshAuthTicket(string userEmail, string password)
        {
            var userid = GetUserid(userEmail, password);
            var token = GetToken(userid);
            if (!string.IsNullOrEmpty(token))
            {
                var authKey = GetAuthKey(token, userid, password);
                // Write the ticket back to file
                var freshAuthTicket = new ToodledoAuthTicket
                                          {
                                              Email = userEmail,
                                              Md5Password = password,
                                              UserId = userid,
                                              Token = token,
                                              AuthKey = authKey,
                                              // Add less than 4 hours to ensure that the ticket does not expire on server side
                                              ExpiryTime = DateTime.Now.AddHours(3).AddMinutes(59)
                                          };
                return freshAuthTicket;
            }
            return null;
        }

        #region Read/Write AuthTicket
        private void WriteTicketToFile(ToodledoAuthTicket freshAuthTicket)
        {
            using (StreamWriter sr = File.CreateText(_ticketFile))
            {
                sr.WriteLine(freshAuthTicket.Email);
                sr.WriteLine(freshAuthTicket.Md5Password.ComputeMd5HashString());
                sr.WriteLine(freshAuthTicket.UserId);
                sr.WriteLine(freshAuthTicket.Token);
                sr.WriteLine(freshAuthTicket.AuthKey);
                sr.WriteLine(freshAuthTicket.ExpiryTime.ToString("u", CultureInfo.InvariantCulture));
            }
        }

        private static ToodledoAuthTicket ReadAuthTicketFromFile(string ticketFile)
        {
            if (File.Exists(ticketFile))
            {
                var ticket = new ToodledoAuthTicket();
                using (var tr = File.OpenText(ticketFile))
                {
                    ticket.Email       = tr.ReadLine();
                    ticket.Md5Password = tr.ReadLine();
                    ticket.UserId      = tr.ReadLine();
                    ticket.Token       = tr.ReadLine();
                    ticket.AuthKey     = tr.ReadLine();
                    ticket.ExpiryTime  = DateTime.ParseExact(tr.ReadLine(), "u", CultureInfo.InvariantCulture);
                }
                return ticket;
            }
            return null;
        }
        #endregion

        private string GetUserid(string userEmail, string password)
        {
            var queryParams = new NameValueCollection
                                  {
                                      {"method", "getUserid"},
                                      {"email", userEmail},
                                      {"pass", password}
                                  };
            var response = _toodledoConnector.GetToodledoResponse(queryParams);
            if (response.Root != null)
            {
                var userId = response.Root.Value;
                if (userId == "0")
                    throw new ArgumentException("Either user email or password is blank");
                if (userId == "1")
                    throw new ArgumentException("Supplied credentials are wrong");
                if (userId.Length < 15 || userId.Length > 16)
                    throw new InvalidDataException("Unable to get correct data from Toodledo");
                return userId;
            }
            return string.Empty;
        }

        string GetToken(string uniqueUserid)
        {
            var queryParams = new NameValueCollection {{"method", "getToken"}, {"userid", uniqueUserid}};
            var response = _toodledoConnector.GetToodledoResponse(queryParams);
            if (response.Root != null)
                if (response.Root.Name == "token")
                    return response.Root.Value;
                else
                    return string.Empty;
            return string.Empty;
        }

        static string GetAuthKey(string token, string userid, string password)
        {
            return (password.ComputeMd5HashString() + token + userid).ComputeMd5HashString();
        }

        #endregion        


    }
}
